/*
    Copyright (C) 2005-2008  Ricky Zheng <ricky_gz_zheng@yahoo.co.nz>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/
/**
 * \file uffs_fs.c
 * \brief basic file operations
 * \author Ricky Zheng, created 12th May, 2005
 */

#include "uffs/uffs_fs.h"
#include "uffs/uffs_config.h"
#include "uffs/ubuffer.h"
#include "uffs/uffs_ecc.h"
#include "uffs/uffs_badblock.h"
#include "uffs/uffs_os.h"
#include <string.h>
#include <stdio.h>

#define PFX "fs:"

#define GET_SERIAL_FROM_NODE(obj) ((obj)->type == UFFS_TYPE_DIR ? (obj)->node->u.dir.serial : (obj)->node->u.file.serial)
#define GET_BLOCK_FROM_NODE(obj) ((obj)->type == UFFS_TYPE_DIR ? (obj)->node->u.dir.block : (obj)->node->u.file.block)

static URET _PrepareOpenObj(uffs_Object *obj, const char *fullname, int oflag, int pmode);
static URET _CreateObjectUnder(uffs_Object *obj);
static void _ReleaseObjectResource(uffs_Object *obj);
static URET _TruncateObject(uffs_Object *obj, u32 remain);

static char _currentDir[MAX_PATH_LENGTH] = {0};

static int _object_buf[sizeof(uffs_Object) * MAX_OBJECT_HANDLE / sizeof(int)];//MAX_OBJECT_HANDLE = 0x0A
//static int _object_buf[sizeof(uffs_Object) * MAX_OBJECT_HANDLE / sizeof(int)];//MAX_OBJECT_HANDLE = 0x0A
static struct ubufm _object_dis = {
	_object_buf,
	sizeof(uffs_Object),
	MAX_OBJECT_HANDLE,
	NULL,
	0,
};

static URET _trimSpacePeriodPath(char *path)
{
    int len;

    len = strlen(path) - 1;
    while(path[len] == 0x20 || path[len] == 0x2e) {
    	path[len] = '\0';
    	len--;
    }

    return U_SUCC;
}

/* "/abc/./def/" ==> "/abc/def/" */
static URET _trimCurPath(char *path)
{
	char *p;

	if (*path) {
		p = path + strlen(path) - 1;
		if (*p == '.') {
			if (p > path) {
				if (*(p-1) == '/') {
					*p = 0;
				}
				else if (*(p-1) != '.') {
					return U_FAIL;
				}
			}
			else {
				*p = 0;
			}
		}
	}

	while((p = strstr(path, "/./")) != NULL) {
		p++;
		while((*p = *(p+2)) != 0) p++;
	}

	return U_SUCC;

}

/*  "/abc/def/../xyz/ ==> "/abc/xyz/"  */
static URET _trimParentPath(char *path)
{
	char *p, *p1;
	while((p = strstr(path, "/../")) != NULL) {
		if (p == path) return U_FAIL; // error

		/* back search the '/' */
		for(p1 = p - 1; p1 >= path && *p1 != '/'; p1--);
		if (p1 < path) return U_FAIL;
		p1++;
		while((*p1 = *(p+4)) != 0) { p1++; p++; }
	}

	if (strlen(path) >= 3) {
		p = path + strlen(path) - 3;
		if (strcmp(p, "/..") == 0) {
			/* back search the '/' */
			for(p1 = p - 1; p1 >= path && *p1 != '/'; p1--);
			if (p1 < path) return U_FAIL;
			*(p1+1) = 0;
		}
	}

	return U_SUCC;
}

/* cur: /abc/  : def/xyz == > /abc/def/xyz
 */
static URET _regulate_path(const char *old_path, char *new_path)
{
	char buf[MAX_PATH_LENGTH+1];
	int len;

	if ((len = strlen(old_path)) >= MAX_PATH_LENGTH) return U_FAIL;
	if (*old_path != '/') {
		_sprintf(buf, "%s%s", _currentDir, old_path);
	}
	else {
		strcpy(buf, old_path);
	}
	len = strlen(buf);
	if (_trimCurPath(buf) != U_SUCC) return U_FAIL;
	if (_trimParentPath(buf) != U_SUCC) return U_FAIL;
	strcpy(new_path, buf);

	return U_SUCC;
}

/**
 * change current path
 * \param[in] path new path
 */
URET uffs_SetCurrentPath(const char *path)
{
	URET ret = U_FAIL;
	uffs_Object obj;
	int len;

	memset(&obj, 0, sizeof(uffs_Object));
	if (uffs_OpenObject(&obj, path, UO_DIR|UO_RDONLY, NULL/*US_IREAD*/) == U_SUCC) {
		uffs_CloseObject(&obj);
		uffs_CriticalEnter();
		ret = _regulate_path(path, _currentDir);
		if (ret == U_SUCC) {
			len = strlen(_currentDir);
			if (_currentDir[len-1] != '/') {
				_currentDir[len] = '/';
				_currentDir[len+1] = 0;
			}
		}
		uffs_CriticalExit();
	}

	return ret;
}

/**
 * get current path
 * \param[in/out] path store the returned current path if path is not NULL
 * \return current path pointer
 */
const char * uffs_GetCurrentPath(char *path)
{
	if (path) {
		strcpy(path, _currentDir);
		return path;
	}
	else {
		return _currentDir;
	}
}

/**
 * get absolute path
 * \param[in] path relative path
 * \param[out] return absolute_path
 * \return #U_SUCC if success, otherwise return #U_FAIL
 */
URET uffs_GetAbsolutePath(const char *path, char *absolute_path)
{
	return _regulate_path(path, absolute_path);
}

/**
 * initialise object buffers, called by UFFS internal
 */
URET uffs_InitObjectBuf(void)
{
	strcpy(_currentDir, "/");

	_object_dis.lock = uffs_SemCreate(1);

	if(uBufInit(&_object_dis) < 0) {
		return U_FAIL;
	}

	return U_SUCC;
}

URET uffs_UninitObjectBuf(void)
{
	uffs_SemDestroy(_object_dis.lock);
	return U_SUCC;
}

/**
 * alloc a new object structure
 * \return the new object
 */
uffs_Object * uffs_GetObject(void)
{
	uffs_Object * obj;
	obj = (uffs_Object *)uBufGet(&_object_dis);
	if (obj) {
		memset(obj, 0, sizeof(uffs_Object));
		obj->openSucc = U_FALSE;
	}
	return obj;
}

/**
 * put the object struct back to system
 */
void uffs_PutObject(uffs_Object *obj)
{
	if(obj) uBufPut(obj, &_object_dis);
}

/**
 * \return the internal index num of object
 */
int uffs_GetObjectIndex(uffs_Object *obj)
{
	return uBufGetIndex(obj, &_object_dis);
}

/**
 * \return the object by the internal index
 */
uffs_Object * uffs_GetObjectByIndex(int idx)
{
	return (uffs_Object *) uBufGetBufByIndex(idx, &_object_dis);
}

static void uffs_ObjectDevLock(uffs_Object *obj)
{
	if (obj) {
		if (obj->dev) {
			uffs_DeviceLock(obj->dev);
			obj->devLockCount++;
		}
	}
}

static void uffs_ObjectDevUnLock(uffs_Object *obj)
{
	if (obj) {
		if (obj->dev) {
			obj->devLockCount--;
			uffs_DeviceUnLock(obj->dev);
		}
	}
}

/** get path from fullname, return path length
 * fullname: "/abc/def"		|	"/abc/def/"		| "/"
 * ==> path: "/abc/"        |   "/abc/def/"		| "/"
 */
static int _getpath(const char *fullname, char *path)
{
	int i;
	int len;

	if (fullname[0] == 0) return 0;

	len = strlen(fullname);
	for (i = len - 1; i > 0 && fullname[i] != '/'; i--);

	i++;
	if (path) {
		memcpy(path, fullname, i);
		path[i] = 0;
	}

	return i;
}

/** get name from fullname
 * fullname: "/abc/def"		|	"/abc/def/"		| "/"
 *  returen: "def"          |   ""		        | ""
 */
static const char * _getname(const char *fullname)
{
	int pos = _getpath(fullname, NULL);

	return fullname + pos;
}

URET uffs_CheckObjectName(char* szName, char *r_fullname)
{
    int len = 0;

	while(szName[len] != '\0'
		&& szName[len] != '<'
		&& szName[len] != '>'
		&& szName[len] != '/'
		&& szName[len] != '\\'
		&& szName[len] != '?'
		&& szName[len] != '*'
		&& szName[len] != '\"'
		&& szName[len] != '|'
		&& szName[len] != ':')
	{
		len++;
	}

	if (szName[len] != '\0') {
		return U_FAIL;
	}

	memcpy(r_fullname, szName, len);
	if (strcmp(r_fullname, "..")) {
		_trimSpacePeriodPath(r_fullname);
	}
	_trimCurPath(r_fullname);
	_trimParentPath(r_fullname);

    if ((len = strlen(r_fullname)) >= MAX_PATH_LENGTH) {
    	return U_FAIL;
    }

    return U_SUCC;
}

URET uffs_RePrepareObject(uffs_Object *obj, const char* szName, int serial, int flag, int mode)
{
    char r_fullname[MAX_PATH_LENGTH+2] = {0};
    int len = 0;

	if (U_SUCC != uffs_CheckObjectName(szName, r_fullname)) {
		obj->err = UEINVAL;
		obj->openSucc = U_FAIL;
		return U_FAIL;
	}

    obj->pos = 0;
	len = strlen(r_fullname);

    if (flag & UO_DIR) {
	    obj->type = UFFS_TYPE_DIR;
	    if (r_fullname[len-1] == '/') {
	    	r_fullname[len-1] = '\0';
	        len--;
	    }
	} else {
		obj->type = UFFS_TYPE_FILE;
		if (r_fullname[len-1] == '/') {
			uffs_Perror(UFFS_ERR_NOISY, PFX"bad file name: %s\n", szName);
			obj->err = UEINVAL;
			obj->openSucc = U_FAIL;
			return U_FAIL;
		}
	}

	obj->father = serial;
	obj->oflag = flag;
	obj->pmode = mode;
	obj->dev->refCount++;

    obj->nameLen = len;
	obj->name = (char*)uffs_MemAlloc(obj->dev, obj->nameLen + 4);
	if (obj->name == NULL) {
        obj->err = UENOMEM;
        return U_FAIL;
    }
	strcpy(obj->name, r_fullname);

	obj->sum = uffs_MakeSum16(obj->name, obj->nameLen);
	return U_SUCC;
}

/**
 * create a new object and open it if success
 */
URET uffs_CreateObjWithPreOpen(uffs_Object *obj)
{
	URET ret = U_SUCC;

	uffs_ObjectDevLock(obj);
	ret  = _CreateObjectUnder(obj);
	uffs_ObjectDevUnLock(obj);

	if (ret != U_SUCC) {
        _ReleaseObjectResource(obj);
	}
	return ret;
}

/** find the matched mount point from path */
static int find_maxMatchedMountPoint(const char *path)
{
	char buf[MAX_PATH_LENGTH+1] = {0};
	int pos;
	uffs_Device *dev;

	pos = strlen(path);
	memcpy(buf, path, pos + 1);

	while(pos > 0) {
		if ((dev = uffs_GetDevice(buf)) != NULL ) {
//			uffs_Perror(UFFS_ERR_NORMAL, "mount:%s\n", buf);
			uffs_PutDevice(dev);
			return pos;
		}
		else {
			buf[pos - 1] = '\0'; //replace the last '/' with '\0'

			//back forward search the next '/'
			for (; pos > 0 && buf[pos-1] != '/'; pos--)
				buf[pos-1] = '\0';
		}
	}

	return pos;
}

/*
URET uffs_CheckFilePath(const char *oldPath, const char *newPath)
{
	int pos = find_maxMatchedMountPoint(oldPath);
	int pos1 = find_maxMatchedMountPoint(newPath);
	if (pos <= 0 || pos <= 0 || pos != pos1) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't moving object to different mount point\n");
		return U_FAIL;
	}

	return U_SUCC;
}
*/
static URET _CreateObjectUnder(uffs_Object *obj)
{
	/**
	 * \note: level 0 has been set in obj.
	 */
	uffs_Buf *buf = NULL;
	uffs_fileInfo fi, *pfi;

	TreeNode *node;

	if (obj->type == UFFS_TYPE_DIR) {
		//find out whether have file with the same name
		node = uffs_FindFileNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
		if (node != NULL) {
			obj->err = UEINVALOBJ;
			obj->openSucc = U_FALSE;
			return U_FAIL;
		}
		obj->node = uffs_FindDirNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
		if (obj->node != NULL) {
			obj->err = UEEXIST;
			obj->openSucc = U_FALSE;
			return U_FAIL;
		}
	}
	else {
		//find out whether have dir with the same name
		node = uffs_FindDirNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
		if (node != NULL) {
			obj->err = UEINVALOBJ;
			obj->openSucc = U_FALSE;
			return U_FAIL;
		}
		obj->node = uffs_FindFileNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
	}

	if (obj->node) {
		if (obj->oflag & UO_EXCL) {
			obj->err = UEEXIST;
			obj->openSucc = U_FALSE;
			return U_FAIL;
		}

		/* dir|file already exist, truncate it to zero length */
		obj->serial = GET_SERIAL_FROM_NODE(obj);

		buf = uffs_BufGetEx(obj->dev, obj->type, obj->node, 0);
		if(buf == NULL) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"found in tree, but can' load buffer ?\n");
			goto err;
		}

		pfi = (uffs_fileInfo *)(buf->data);

		if (pfi->attr & US_DENY) {
		    obj->err = UESHARE;
			obj->openSucc = U_FALSE;
			uffs_BufPut(obj->dev, buf);
			return U_FAIL;
		}

	    if (pfi->reserved == 0) {
			pfi->reserved = obj->handle;
   		}

//        if (obj->pmode & (UA_MASK)) {
//            pfi->attr &= ~UA_MASK;
//        }
//        pfi->attr |= obj->pmode;

		pfi->attr++;//it make the lowest byte as reference count.
		uffs_BufWrite(obj->dev, buf, pfi, 0, sizeof(u32)*5);

		uffs_BufPut(obj->dev, buf);

		obj->err = UEEXIST;
		obj->openSucc = U_TRUE;

		return U_SUCC;
	}
/*
		obj->pos = 0;

		obj->openSucc = U_TRUE;
		ret = _TruncateObject(obj, 0);
		if (ret != U_SUCC) {
			obj->openSucc = U_FALSE;
		}
		return ret;
	}*/

	/* dir|file does not exist, create a new one */
	obj->serial = uffs_FindFreeFsnSerial(obj->dev);

	if(obj->dev->tree.erasedCount < MINIMUN_ERASED_BLOCK) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"insufficient block in create obj\n");
		obj->err = UENOSPACE;
		goto err;
	}

	buf = uffs_BufNew(obj->dev, obj->type, obj->father, obj->serial, 0);
	if(buf == NULL) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"Can't new buffer when create obj!\n");
		goto err;
	}

	memset(&fi, 0, sizeof(uffs_fileInfo));
	memcpy(fi.name, obj->name, obj->nameLen);
	fi.name[obj->nameLen] = '\0';
	fi.name_len = obj->nameLen;
	fi.access = 0;
	fi.attr |= obj->pmode;

	if (obj->type == UFFS_TYPE_DIR) {
		fi.attr |= FILE_ATTR_DIR;
	}
	else {
		fi.reserved = obj->handle;
		fi.attr++;
		fi.attr |= FILE_ATTR_FILE;
	}
	fi.createTime = fi.lastModify = uffs_GetCurDateTime();

	obj->createTime = fi.createTime;
	obj->lastModify = fi.lastModify;
	obj->attr = fi.attr;
	obj->access = fi.access;

	uffs_BufWrite(obj->dev, buf, &fi, 0, sizeof(uffs_fileInfo));
	uffs_BufPut(obj->dev, buf);

	//flush buffer immediately, so that the new node will be inserted into the tree
	uffs_BufFlush(obj->dev);

	//update obj->node: after buf flushed, the NEW node can be found in the tree
	if (obj->type == UFFS_TYPE_DIR)
		obj->node = uffs_FindDirNodeFromTree(obj->dev, obj->serial);
	else
		obj->node = uffs_FindFileNodeFromTree(obj->dev, obj->serial);

	if(obj->node == NULL) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't find the node in the tree ?\n");
	    //should goto err, but.....
	    //goto err;
	}

	if (obj->type == UFFS_TYPE_DIR) {
		//do nothing for dir ...
	}
	else {
		obj->node->u.file.len = 0;	//init the length to 0
	}

	if (HAVE_BADBLOCK(obj->dev)) uffs_RecoverBadBlock(obj->dev);
	obj->openSucc = U_TRUE;

	return U_SUCC;

err:
	if (buf && obj->dev) {
		uffs_BufPut(obj->dev, buf);
		buf = NULL;
	}

	return U_FAIL;
}

static URET _OpenObjectUnder2(uffs_Object *obj)
{
    uffs_Buf *buf = NULL;
	uffs_fileInfo *fi = NULL;

	/*************** init level 1 ***************/
	obj->node = uffs_FindDirNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
	if (obj->node == NULL) {
		obj->node = uffs_FindFileNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
		obj->type = UFFS_TYPE_FILE;
	}

	if(obj->node == NULL) {
	    obj->err = UENOENT;
			//uffs_Perror(UFFS_ERR_NOISY, PFX"dir or file not found\n");
		return U_FAIL;
	}

	obj->serial = GET_SERIAL_FROM_NODE(obj);

	buf = uffs_BufGetEx(obj->dev, obj->type, obj->node, 0);
	if(buf == NULL) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get buf when open!\n");
		return U_FAIL;
	}

	fi = (uffs_fileInfo *)(buf->data);

	if (fi->attr & US_DENY) {
	    obj->err = UESHARE;
		obj->openSucc = U_FALSE;
		uffs_BufPut(obj->dev, buf);
		return U_FAIL;
	}

    if (fi->reserved == 0) {
   		if ((obj->oflag & UO_LOOKUP) && (obj->type == UFFS_TYPE_FILE)) {
   			fi->reserved = obj->err;
   			obj->handle = obj->err;
   		} else {
   			fi->reserved = obj->handle;
   		}
    }
/*
    if (obj->pmode & US_MASK) {
        fi->attr &= ~US_MASK;
    }
*/
    if (obj->pmode & (UA_MASK)) {
        fi->attr &= ~UA_MASK;
    }
    fi->attr |= obj->pmode;

    fi->attr++;//it makes 1 low bytes as reference.
	uffs_BufWrite(obj->dev, buf, fi, 0, sizeof(u32)*5);

	uffs_BufPut(obj->dev, buf);

//	if((obj->oflag & (UO_CREATE | UO_EXCL)) == (UO_CREATE | UO_EXCL)){
//		obj->err = UEEXIST;
//		return U_FAIL;
//	}

	obj->openSucc = U_TRUE;

//	if(obj->oflag & UO_TRUNC) {
//		if(_TruncateObject(obj, 0) == U_FAIL){
//			//obj->err will be set in _TruncateObject
//			return U_FAIL;
//		}
//	}

	obj->err = UENOERR;

	return U_SUCC;
}

static URET _OpenObjectUnderForDelete(uffs_Object *obj)
{
    uffs_Buf *buf = NULL;
	uffs_fileInfo *fi = NULL;

	/*************** init level 1 ***************/
	if (obj->type == UFFS_TYPE_DIR) {
		obj->node = uffs_FindDirNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
		if (obj->node == NULL) {
			obj->node = uffs_FindFileNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
			if (obj->node != NULL) {
				obj->err = UEINVALOBJ;
				return U_FAIL;
			}
		}
	}
	else {
		obj->node = uffs_FindFileNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
		if (obj->node == NULL) {
			obj->node = uffs_FindDirNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
			if (obj->node != NULL) {
				obj->err = UEINVALOBJ;
				return U_FAIL;
			}
		}
	}

	if(obj->node == NULL) {
	    obj->err = UENOENT;
		uffs_Perror(UFFS_ERR_NOISY, PFX"dir or file not found\n");
		return U_FAIL;
	}

	obj->serial = GET_SERIAL_FROM_NODE(obj);

	buf = uffs_BufGetEx(obj->dev, obj->type, obj->node, 0);
	if(buf == NULL) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get buf when open!\n");
		return U_FAIL;
	}

	fi = (uffs_fileInfo *)(buf->data);

	if ((fi->attr & US_DENY) || !(fi->attr & UA_RDWR)) {
	    obj->err = UESHARE;
		obj->openSucc = U_FALSE;
		uffs_BufPut(obj->dev, buf);
		return U_FAIL;
	}

	if (fi->attr & 0x000000ff || fi->reserved != 0) {
		obj->err = UEACCES;
		obj->openSucc = U_FALSE;
		uffs_BufPut(obj->dev, buf);
		return U_FAIL;
	}

	fi->reserved = obj->handle;

    if (obj->pmode & US_MASK) {
        fi->attr &= ~US_MASK;
    }
    if (obj->pmode & UA_MASK) {
        fi->attr &= ~UA_MASK;
    }
    fi->attr |= obj->pmode;

    fi->attr++;//it makes 1 low bytes as reference.
	uffs_BufWrite(obj->dev, buf, fi, 0, sizeof(u32)*5);

	uffs_BufPut(obj->dev, buf);

//	if((obj->oflag & (UO_CREATE | UO_EXCL)) == (UO_CREATE | UO_EXCL)){
//		obj->err = UEEXIST;
//		return U_FAIL;
//	}

	obj->openSucc = U_TRUE;

//	if(obj->oflag & UO_TRUNC) {
//		if(_TruncateObject(obj, 0) == U_FAIL){
//			//obj->err will be set in _TruncateObject
//			return U_FAIL;
//		}
//	}

	obj->err = UENOERR;

	return U_SUCC;
}

//static URET _OpenObjectUnder(uffs_Object *obj)
//{
//	uffs_Buf *buf = NULL;
//	uffs_fileInfo *fi = NULL;
//
//	/*************** init level 1 ***************/
//	if (obj->type == UFFS_TYPE_DIR) {
//		obj->node = uffs_FindDirNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
//	}
//	else {
//		obj->node = uffs_FindFileNodeByName(obj->dev, obj->name, obj->nameLen, obj->sum, obj->father);
//	}
//
//	if(obj->node == NULL) {
//		/* dir|file not exist */
//		if(obj->oflag & UO_CREATE){
//			//create dir|file
//			return _CreateObjectUnder(obj);
//		}
//		else {
//			obj->err = UENOENT;
//			//uffs_Perror(UFFS_ERR_NOISY, PFX"dir or file not found\n");
//			return U_FAIL;
//		}
//	}
//
//	obj->serial = GET_SERIAL_FROM_NODE(obj);
//
//	buf = uffs_BufGetEx(obj->dev, obj->type, obj->node, 0);
//	if(buf == NULL) {
//		uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get buf when open!\n");
//		return U_FAIL;
//	}
//
//	fi = (uffs_fileInfo *)(buf->data);
//	obj->attr = fi->attr;
//	obj->createTime = fi->createTime;
//	obj->lastModify = fi->lastModify;
//	obj->access = fi->access;

/**
  //this open function only used by OpenFindObject.
  // so the belows info is not nesseccery.
	if (fi->attr & US_DENY) {
	    obj->err = UESHARE;
		obj->openSucc = U_FALSE;
		return U_FAIL;
	}

	if (fi->reserved == 0) {
		fi->reserved = obj->handle;
		uffs_BufWrite(obj->dev, buf, fi, 0, sizeof(u32)*5);
	}
*/
//	uffs_BufPut(obj->dev, buf);

//	if((obj->oflag & (UO_CREATE | UO_EXCL)) == (UO_CREATE | UO_EXCL)) {
//		obj->err = UEEXIST;
//		return U_FAIL;
//	}

//	obj->openSucc = U_TRUE;

//	if(obj->oflag & UO_TRUNC) {
//		if(_TruncateObject(obj, 0) == U_FAIL){
//			//obj->err will be set in _TruncateObject
//			return U_FAIL;
//		}
//	}

//	obj->err = UENOERR;


//	return U_SUCC;
//}

static int getPathPart(const char *path, int *pos)
{
	int len = 0;

	for (len = 0; path[len+(*pos)] != '\0' && path[len+(*pos)] != '/'; len++) ;

	*pos += len;

	return len;
}

/** get dir's serial No.
 *  path is the relative path from mount point: "abc/def/"  |  "" (in this case, return father)
 */
static u16 _GetDirSerial(uffs_Device *dev, const char *path, u16 father)
{
	int pos = 0;
	int pos_last = 0;
	int len = 0;
	char part[MAX_FILENAME_LENGTH+1] = {0};
	TreeNode * node;
	u16 sum;
	u16 serial = father;

	while((len = getPathPart(path, &pos)) > 0) {
		memcpy(part, path + pos_last, len);
		part[len] = '\0';
		sum = uffs_MakeSum16(part, len);
		node = uffs_FindDirNodeByName(dev, part, len, sum, serial);
		if (node) {
			serial = node->u.dir.serial;
			pos_last = ++pos;
		}
		else {
			return INVALID_UFFS_SERIAL;
		}
	}

	return serial;
}

/** prepare the object struct for open */
static URET _PrepareOpenObj(uffs_Object *obj, const char *fullname, int oflag, int pmode)
{
	char buf[MAX_PATH_LENGTH+1] = {0};//
	char mount[MAX_FILENAME_LENGTH+1] = {0};
	char r_fullname[MAX_PATH_LENGTH+2] = {0};
	char *start_path = mount;
	int pos;
	int i, len;
	u8 type;

	type = (oflag & UO_DIR) ? UFFS_TYPE_DIR : UFFS_TYPE_FILE;

	obj->dev = NULL;
	obj->name = NULL;

	if((oflag & (UO_WRONLY | UO_RDWR)) == (UO_WRONLY | UO_RDWR)){
		/* UO_WRONLY and UO_RDWR can't appear together */
		uffs_Perror(UFFS_ERR_NOISY, PFX"UO_WRONLY and UO_RDWR can't appear together\n");
		obj->err = UEINVAL;
		return U_FAIL;
	}

	obj->pos = 0;

	if (!fullname || _regulate_path(fullname, r_fullname) != U_SUCC) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"bad file/dir name: %s\n", fullname);
		obj->err = UEINVAL;
		return U_FAIL;
	}

	/************** init level 0 **************/
	obj->oflag = oflag;
	obj->pmode = pmode;

	len = strlen(r_fullname);

	// assume: r_fullname: "/abc/def/xyz"  | "/abc/"  |  "/abc"	|	"/"
	if (oflag & UO_DIR) {
		/** open a directory */
		obj->type = UFFS_TYPE_DIR;

		// regulate the dir name
		if (r_fullname[len-1] != '/') {
			r_fullname[len] = '/';
			r_fullname[len+1] = 0;
			len++;
		}
		// r_fullname: "/abc/def/xyz/"  |  "/abc/"   |  "/abc/"	|   "/"
	}
	else {
		/** open a file */
		obj->type = UFFS_TYPE_FILE;

		if (r_fullname[len-1] == '/') {
			uffs_Perror(UFFS_ERR_NOISY, PFX"bad file name: %s\n", fullname);
			obj->err = UEINVAL;
			return U_FAIL;
		}

		// r_fullname: "/abc/def/xyz"  |  X  | "/abc"  |  X
	}

	_getpath(r_fullname, buf);
	// get path from full name, now what's on buf:
	//	if it's dir:        "/abc/def/xyz/"  |  "/abc/"  |   "/abc/"  |  "/"
	//  if it's file:       "/abc/def/"      |  X        |   "/"      |  X


	pos = find_maxMatchedMountPoint(buf);
	if (pos == 0) {
		/* can't not find any mount point from the path ??? */
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't find any mount point from the path %s\n", buf);
		obj->err = UENOENT;
		return U_FAIL;
	}

	// get mount point: "/abc/"	or "/"
	memcpy(mount, buf, pos);
	mount[pos] = '\0';

	obj->dev = uffs_GetDevice(mount);
	if (obj->dev == NULL) {
		// uffs_Perror(UFFS_ERR_NOISY, "Can't get device from mount point: %s\r\n", mount);
		obj->err = UENOENT;
		return U_FAIL;
	}

	//here is a good chance to deal with bad block ...
	if (HAVE_BADBLOCK(obj->dev)) uffs_RecoverBadBlock(obj->dev);

	// get the rest name (r_fullname - mount point) to buf:
	//   if the mount point is "/abc/", then:
	//		if it's dir:   "def/xyz/"     |   ""      |  ""     |  X
	//		if it's file:  "def/xyz"      |   X       |  X      |  X
	//   if the mount point is "/", then:
	//      if it's dir:   "abc/def/xyz/  |   "abc/"  |  "abc/" |  ""
	//      if it's file:  "abc/def/xyz"  |   X       |  "abc"  |  X
	len = strlen(r_fullname) - strlen(mount);
	memcpy(buf, r_fullname + strlen(mount), len);
	buf[len] = '\0';

	if (len == 0 && (oflag & UO_DIR) == 0) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"bad file name: %s\n", fullname);
		obj->err = UEINVAL;
		goto err;
	}

	if (oflag & UO_DIR) {
		if (len == 0) {

			// uffs_Perror(UFFS_ERR_NOISY, "Zero length name ? root dir ?\r\n");

			obj->father = PARENT_OF_ROOT;
			obj->serial = ROOT_DIR_ID;
			obj->nameLen = 0;
			obj->name = NULL;
		}
		else {
			if (buf[len-1] == '/') {
				buf[len-1] = 0;
				len--;
			}
			for (i = len - 1; i >= 0 && buf[i] != '/'; i--);
			obj->nameLen = len - i - 1;
			obj->name = (char *) uffs_MemAlloc(obj->dev, obj->nameLen + 4);
			if (obj->name == NULL) {
				obj->err = UENOMEM;
				goto err;
			}
			if (obj->name) strcpy(obj->name, buf + i + 1);
			if (i < 0) {
				obj->father = ROOT_DIR_ID;
			}
			else {
				buf[i+1] = 0;
				obj->father = _GetDirSerial(obj->dev, buf, ROOT_DIR_ID);
			}
		}
	}
	else {
		for(i = len - 1; i >= 0 && buf[i] != '/'; i--);
		obj->nameLen = len - i - 1;
		obj->name = (char *) uffs_MemAlloc(obj->dev, obj->nameLen + 4);
		if (obj->name == NULL) {
			obj->err = UENOMEM;
			goto err;
		}

		if (obj->name) strcpy(obj->name, buf + i + 1);
		if (i < 0) {
			obj->father = ROOT_DIR_ID;
		}
		else {
			buf[i+1] = 0;
			obj->father = _GetDirSerial(obj->dev, buf, ROOT_DIR_ID);
		}
	}

	if (obj->father == INVALID_UFFS_SERIAL) {
		uffs_Perror(UFFS_ERR_NORMAL, PFX"can't open path %s\n", start_path);
		obj->err = UENOENT;
		goto err;
	}

	if(obj->name == NULL && obj->nameLen > 0) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"open, allocate mem fail when open\n");
		obj->err = UENOMEM;
		goto err;
	}

	obj->sum = uffs_MakeSum16(obj->name, obj->nameLen);
	obj->encode = UFFS_DEFAULT_ENCODE;
	obj->pagesOnHead = obj->dev->attr.pages_per_block - 1;

	//uffs_Perror(UFFS_ERR_NOISY, "Prepare name: %s\r\n", obj->name);

	return U_SUCC;

err:
	if (obj->name) {
		uffs_MemFree(obj->dev, obj->name);
		obj->name = NULL;
	}
	if (obj->dev) {
		uffs_PutDevice(obj->dev);
		obj->dev = NULL;
	}

	return U_FAIL;
}

URET uffs_OpenObjectRoot(uffs_Object *obj, const char *fullname, int oflag, int pmode)
{
	URET ret = U_FAIL;
	if ((ret = _PrepareOpenObj(obj, fullname, oflag, pmode)) == U_SUCC) {
	    obj->nameLen = 1;
	    obj->name = (char *) uffs_MemAlloc(obj->dev, obj->nameLen + 4);
	    if (obj->name == NULL) {
            obj->err = UENOMEM;
            return U_FAIL;
        }
	    strcpy(obj->name, "/");
		obj->sum = uffs_MakeSum16(obj->name, obj->nameLen);
	}

	return ret;
}

URET uffs_OpenObjectWithPreOpen(uffs_Object *obj)
{
    URET ret = U_FAIL;

	uffs_ObjectDevLock(obj);
	ret = _OpenObjectUnder2(obj);
	uffs_ObjectDevUnLock(obj);

	if (ret == U_FAIL) {
		_ReleaseObjectResource(obj);
	}

    return ret;
}

//Don't use this funtion
URET uffs_OpenObject(uffs_Object *obj, const char *fullname, int oflag, int pmode)
{
	URET ret = U_FAIL;

	if ((ret = _PrepareOpenObj(obj, fullname, oflag, pmode)) == U_SUCC) {
		if (obj->nameLen > 0) {
			uffs_ObjectDevLock(obj);
			ret = _OpenObjectUnder2(obj);
			uffs_ObjectDevUnLock(obj);
		}
	}

	if(ret == U_FAIL)
		_ReleaseObjectResource(obj);

	return ret;
}

static void _ReleaseObjectResource(uffs_Object *obj)
{
	if (obj) {
		if(obj->name) {
			uffs_MemFree(obj->dev, obj->name);
			obj->name = NULL;
		}
		if (obj->dev) {
			if (HAVE_BADBLOCK(obj->dev)) uffs_RecoverBadBlock(obj->dev);
			if (obj->devLockCount > 0) {
				uffs_ObjectDevUnLock(obj);
			}
			uffs_PutDevice(obj->dev);
			obj->dev = NULL;
		}
	}
}

URET uffs_CloseObject(uffs_Object *obj)
{
	uffs_Device *dev;
	uffs_Buf *buf;
	uffs_fileInfo fi;

	if(obj == NULL || obj->dev == NULL) return U_FAIL;
	if (obj->openSucc != U_TRUE) goto out;

	dev = obj->dev;
	uffs_ObjectDevLock(obj);

	//if(obj->oflag & (UO_WRONLY|UO_RDWR |UO_APPEND|UO_CREATE|UO_TRUNC)) {

#ifdef CHANGE_MODIFY_TIME
		if (obj->node) {
			//need to change the last modify time stamp
			if (obj->type == UFFS_TYPE_DIR)
				buf = uffs_BufGetEx(dev, UFFS_TYPE_DIR, obj->node, 0);
			else
				buf = uffs_BufGetEx(dev, UFFS_TYPE_FILE, obj->node, 0);

			if(buf == NULL) {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get file header\n");
				uffs_BufFlush(dev);
				uffs_ObjectDevUnLock(obj);
				goto out;
			}
			uffs_BufRead(dev, buf, &fi, 0, sizeof(uffs_fileInfo));
			//fi.lastModify = uffs_GetCurDateTime();
			if (fi.reserved == obj->handle) {
			    if (obj->type == UFFS_TYPE_FILE) {
                    fi.attr &= ~US_MASK;
                    fi.attr |= US_WRITE;
                }
				fi.reserved = 0;
			}
			fi.attr--;

			uffs_BufWrite(dev, buf, &fi, 0, sizeof(uffs_fileInfo));
			uffs_BufPut(dev, buf);
		}
#endif

		uffs_BufFlush(dev);
	//}

	uffs_ObjectDevUnLock(obj);

out:
	_ReleaseObjectResource(obj);

	return U_SUCC;
}

static u16 _GetFdnByOfs(uffs_Object *obj, u32 ofs)
{
	uffs_Device *dev = obj->dev;
	if(ofs < obj->pagesOnHead * dev->com.pgDataSize) {
		return 0;
	}
	else {
		ofs -= obj->pagesOnHead * dev->com.pgDataSize;
		return (ofs / (dev->com.pgDataSize * dev->attr.pages_per_block)) + 1;
	}
}

//static u16 _GetPageIdByOfs(uffs_Object *obj, u32 ofs)
//{
//	uffs_Device *dev = obj->dev;
//	if(ofs < obj->pagesOnHead * dev->com.pgDataSize) {
//		return (ofs / dev->com.pgDataSize) + 1; //in file header, pageID start from 1, not 0
//	}
//	else {
//		ofs -= (obj->pagesOnHead * dev->com.pgDataSize);
//		ofs %= (dev->com.pgDataSize * dev->attr.pages_per_block);
//		return ofs / dev->com.pgDataSize;
//	}
//}
//
//static UBOOL _IsAtTheStartOfBlock(uffs_Object *obj, u32 ofs)
//{
//	uffs_Device *dev = obj->dev;
//	int n;
//
//	if((ofs % dev->com.pgDataSize) != 0) return U_FALSE;
//	if(ofs < obj->pagesOnHead * dev->com.pgDataSize) {
//		return U_FALSE;
//	}
//	else {
//		n = ofs - (obj->pagesOnHead * dev->com.pgDataSize);
//		if(n % (dev->com.pgDataSize * dev->attr.pages_per_block) == 0) return U_TRUE;
//	}
//
//	return U_FALSE;
//}
//
static u32 _GetStartOfDataBlock(uffs_Object *obj, u16 fdn)
{
	if(fdn == 0) {
		return 0;
	}
	else {
		return (obj->pagesOnHead * obj->dev->com.pgDataSize) +
			(fdn - 1) * (obj->dev->com.pgDataSize * obj->dev->attr.pages_per_block);
	}
}


static int _WriteNewBlock(uffs_Object *obj,
						  const void *data, u32 len,
						  u16 father,
						  u16 serial)
{
	uffs_Device *dev = obj->dev;
	u16 pageID;
	int wroteSize = 0;
	int size;
	uffs_Buf *buf;
	URET ret;

    //uffs_Perror(UFFS_ERR_NORMAL, PFX"_WriteNewBlock -->in\n");
	for(pageID = 0; pageID < dev->attr.pages_per_block; pageID++) {
		size = (len - wroteSize) > dev->com.pgDataSize ?
			dev->com.pgDataSize : len - wroteSize;
		if(size <= 0) break;

		buf = uffs_BufNew(dev, UFFS_TYPE_DATA, father, serial, pageID);
		if(buf == NULL) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't create a new page ?\n");
			break;
		}
		ret = uffs_BufWrite(dev, buf, (u8 *)data + wroteSize, 0, size);
		uffs_BufPut(dev, buf);

		if(ret != U_SUCC) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"write data fail!\n");
			break;
		}
		wroteSize += size;
		obj->node->u.file.len += size;
	}

    //uffs_Perror(UFFS_ERR_NORMAL, PFX"_WriteNewBlock -->out\n");
	return wroteSize;
}

static int _WriteInternalBlock(uffs_Object *obj,
							   TreeNode *node,
							   u16 fdn,
							   const void *data,
							   u32 len,
							   u32 blockOfs)
{
	uffs_Device *dev = obj->dev;
	u16 maxPageID;
	u16 pageID;
	u32 size;
	u32 pageOfs;
	u32 wroteSize = 0;
	URET ret;
	uffs_Buf *buf;
	u32 startOfBlock;
	u8 type;
	u16 father, serial;

    //uffs_Perror(UFFS_ERR_NORMAL, PFX"_WriteInternalBlock -->in\n");
	startOfBlock = _GetStartOfDataBlock(obj, fdn);
	if(fdn == 0) {
		type = UFFS_TYPE_FILE;
		father = node->u.file.father;
		serial = node->u.file.serial;
	}
	else {
		type = UFFS_TYPE_DATA;
		father = node->u.data.father;
		serial = fdn;
	}

	if(fdn == 0) maxPageID = obj->pagesOnHead;
	else maxPageID = dev->attr.pages_per_block - 1;


	while(wroteSize < len) {
		pageID = blockOfs / dev->com.pgDataSize;
		if(fdn == 0) pageID++; //in file header, pageID start from 1, not 0.
		if(pageID > maxPageID) break;

		pageOfs = blockOfs % dev->com.pgDataSize;
		size = (len - wroteSize + pageOfs) > dev->com.pgDataSize ?
			(dev->com.pgDataSize - pageOfs) : (len - wroteSize);

		if((obj->node->u.file.len % dev->com.pgDataSize) == 0 &&
			(blockOfs + startOfBlock) == obj->node->u.file.len) {

			buf = uffs_BufNew(dev, type, father, serial, pageID);
			if(buf == NULL) {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"can create a new buf!\n");
				break;
			}
		}
		else {
			buf = uffs_BufGetEx(dev, type, node, pageID);
			if(buf == NULL) {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get buffer ?\n");
				break;
			}
		}

		ret = uffs_BufWrite(dev, buf, (u8 *)data + wroteSize, pageOfs, size);
		uffs_BufPut(dev, buf);
		if(ret == U_FAIL) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"write inter data fail!\n");
			break;
		}

		wroteSize += size;
		blockOfs += size;

		if(startOfBlock + blockOfs > obj->node->u.file.len)
			obj->node->u.file.len = startOfBlock + blockOfs;

	}
    //uffs_Perror(UFFS_ERR_NORMAL, PFX"_WriteInternalBlock -->out\n");
	return wroteSize;
}

static URET _LoadObjectInfo(uffs_Device *dev, TreeNode *node, uffs_ObjectInfo *info, int type)
{
	uffs_Buf *buf;

	buf = uffs_BufGetEx(dev, (u8)type, node, 0);
	if(buf == NULL) {
		return U_FAIL;
	}

	memcpy(&(info->info), buf->data, sizeof(uffs_fileInfo));

	if (type == UFFS_TYPE_DIR) {
		info->len = 0;
		info->serial = node->u.dir.serial;
	}
	else {
		info->len = node->u.file.len;
		info->serial = node->u.file.serial;
	}

	uffs_BufPut(dev, buf);

	return U_SUCC;
}

static URET _PutObjectInfo(uffs_Object *obj, TreeNode *node, int data, int field, int type)
{
	URET ret;
	uffs_Buf *buf;
	uffs_fileInfo localFileInfo;

	buf = uffs_BufGetEx(obj->dev, (u8)type, node, 0);
	if(buf == NULL) {
		return U_FAIL;
	}

    obj->err = 0;
	memcpy(&localFileInfo, buf->data, sizeof(uffs_fileInfo));

	if (localFileInfo.attr & FILE_ATTR_FILE) {
		if (localFileInfo.reserved != obj->handle) {
			if (!(localFileInfo.attr & US_WRITE)) {
				obj->err = UESHARE;
				uffs_BufPut(obj->dev, buf);
				return U_FAIL;
	        }

			if (field == SHA_MODE) {
				if (data & US_WRITE) {
					uffs_BufPut(obj->dev, buf);
					return U_SUCC;
				}

				obj->err = UESHARE;
				uffs_BufPut(obj->dev, buf);
				return U_FAIL;
			}
		} else {
			if (field == SHA_MODE) {
				if (localFileInfo.attr & US_WRITE) {
					if (data & US_WRITE) {
						uffs_BufPut(obj->dev, buf);
						return U_SUCC;
					} else {
					    if (NR_HANDLE(localFileInfo.attr) > 1) {
					    	obj->err = UESHARE;
							uffs_BufPut(obj->dev, buf);
							return U_FAIL;
					    }
					}
				} else {
				    if ((localFileInfo.attr & US_MASK) == (data)) {
				    	uffs_BufPut(obj->dev, buf);
				    	return U_SUCC;
				    }
				}
			}
		}
	} else if (field == SHA_MODE) {
	    uffs_BufPut(obj->dev, buf);
	    return U_SUCC;
	}

	if ((ACC_TIME == field || MOD_TIME == field || CRE_TIME == field)
		&& !(localFileInfo.attr & UA_RDWR)) {
		obj->err = UEACCESS;
	    uffs_BufPut(obj->dev, buf);
	    return U_FAIL;
	}

    if (field == ACC_MODE) {
        localFileInfo.attr &= ~UA_MASK;
	    localFileInfo.attr |= data;
    } else if (field == SHA_MODE) {
        localFileInfo.attr &= ~US_MASK;
        localFileInfo.attr |= data;
	} else if (field == ACC_TIME) {
        localFileInfo.access = data;
	} else if (field == MOD_TIME) {
	    localFileInfo.lastModify = data;
	} else if (field == CRE_TIME) {
	    localFileInfo.createTime = data;
	}

	ret = uffs_BufWrite(obj->dev, buf, &localFileInfo, 0, sizeof(u32) * 4);

	uffs_BufPut(obj->dev, buf);

	return ret;
}

//len must be <= 200
static URET _AppendObject(uffs_Object *obj, int len)
{
	uffs_Device *dev = obj->dev;
	TreeNode *fnode = obj->node;
	int remain = len;
	u16 fdn;
	u32 write_start;
	TreeNode *dnode;
	u32 size;
	unsigned char data[200] = {0};

	if (len > 200) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't Append too max!\n");
		return 0;
	}

	if (obj->type == UFFS_TYPE_DIR) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't write to an dir object!\n");
		return 0;
	}

	obj->pos = fnode->u.file.len;
	while(remain > 0) {
		write_start = obj->pos + len - remain;
		if(write_start > fnode->u.file.len) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"write point out of file ?\n");
			break;
		}

		fdn = _GetFdnByOfs(obj, write_start);

		if(write_start == fnode->u.file.len && fdn > 0 &&
			write_start == _GetStartOfDataBlock(obj, fdn)) {
			if(dev->tree.erasedCount < MINIMUN_ERASED_BLOCK) {
				uffs_Perror(UFFS_ERR_NOISY, PFX"insufficient block in write obj, new block\n");
				obj->err = UENOSPACE;
				break;
			}
			size = _WriteNewBlock(obj, (u8 *)data + len - remain, remain, fnode->u.file.serial, fdn);

			//Flush immediately, so that the new data node will be created and put in the tree.
			uffs_BufFlush(dev);

			if(size == 0) break;
			remain -= size;
		}
		else {
			if(fdn == 0)
				dnode = obj->node;
			else
				dnode = uffs_FindDataNode(dev, fnode->u.file.serial, fdn);

			if(dnode == NULL) {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't find data node in tree ?\n");
				break;
			}
			size = _WriteInternalBlock(obj, dnode, fdn,
									(u8 *)data + len - remain, remain,
									write_start - _GetStartOfDataBlock(obj, fdn));
#ifdef FLUSH_BUF_AFTER_WRITE
			uffs_BufFlush(dev);
#endif
			if(size == 0) break;
			remain -= size;
		}
	}

	obj->pos += (len - remain);

	if (HAVE_BADBLOCK(dev)) uffs_RecoverBadBlock(dev);

	return len - remain;
}

static URET _SpreadObjectSize(uffs_Object *obj, int nSize)
{
    u32 retValue = 0;
    int nWritten = 0;

	if (uffs_GetDeviceFree(obj->dev) >= nSize) {
		while (nWritten + 200 < nSize) {
			retValue = _AppendObject(obj, 200);
			if (retValue < 200) {
				nWritten += retValue;
				if (nWritten > 0) {
					_TruncateObject(obj, nWritten);
				}

				return U_FAIL;
			}
		}

		retValue = _AppendObject(obj, nSize - nWritten);
		if (retValue < nSize - nWritten) {
			nWritten += retValue;
			if (nWritten > 0) {
				_TruncateObject(obj, nWritten);
			}

			return U_FAIL;
		}

		return U_SUCC;
	}

	return U_FAIL;
}

int uffs_SetObjectSize(uffs_Object *obj, int len)
{
    TreeNode *fnode = obj->node;
    URET ret = U_FAIL;
    int nSize;

//check permission
	uffs_ObjectInfo info;

	if (obj == NULL || obj->dev == NULL || obj->openSucc == U_FALSE) {
		uffs_Perror(UFFS_ERR_NORMAL, "error! should assert\n");
		return U_FAIL;
	}

    uffs_ObjectDevLock(obj);

    if (U_SUCC != _LoadObjectInfo(obj->dev, obj->node, &info, obj->type)) {
		obj->err = UENOMEM;
		goto exit1;
	}

    if (info.info.attr & FILE_ATTR_FILE) {
        if ((info.info.reserved != obj->handle) && !(info.info.attr & US_WRITE)) {
    		obj->err = UESHARE;
            goto exit1;
    	}
    }

    if (!(info.info.attr & UA_RDWR)) {
		obj->err = UEACCESS;
        goto exit1;
	}

	nSize = len - fnode->u.file.len;

    if (nSize == 0) {
        ret = U_SUCC;
        goto exit1;
    }

	if (nSize > 0) {
		ret = _SpreadObjectSize(obj, nSize);
		obj->err = UENOSPACE;
	} else {
	    ret = _TruncateObject(obj, len);
	}

exit1:
	uffs_ObjectDevUnLock(obj);
	return ret;
}

/**
 * write data to obj, from obj->pos
 * \param[in] obj file obj
 * \param[in] data data pointer
 * \param[in] len length of data to be write
 * \return bytes wrote to obj
 */
int uffs_WriteObject(uffs_Object *obj, const void *data, int offset, int len)
{
	uffs_Device *dev = obj->dev;
	TreeNode *fnode = obj->node;
	int remain = len;
	u16 fdn;
	u32 write_start;
	TreeNode *dnode;
	u32 size;

//check permission
	uffs_ObjectInfo info;

	if (obj == NULL) return 0;
	if (obj->dev == NULL || obj->openSucc == U_FALSE) return 0;

	if (obj->type == UFFS_TYPE_DIR) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't write to an dir object!\n");
		return 0;
	}
/*
	if((u32)offset > fnode->u.file.len) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't write file out of range!\n");
		return 0; //can't write file out of range
	}

	if(obj->oflag == UO_RDONLY) return 0;
*/

	uffs_ObjectDevLock(obj);

	/*if(obj->oflag & UO_APPEND) {
		obj->pos = fnode->u.file.len;
	}*/

	if (U_SUCC != _LoadObjectInfo(dev, obj->node, &info, obj->type)) {
		uffs_ObjectDevUnLock(obj);
		obj->err = UENOMEM;
		return 0;
	}

    if ((info.info.reserved != obj->handle) && !(info.info.attr & US_WRITE)) {
		obj->err = UESHARE;
		uffs_ObjectDevUnLock(obj);
		return 0;
	}

    if (!(info.info.attr & UA_RDWR)) {
		obj->err = UEACCESS;
		uffs_ObjectDevUnLock(obj);
		return 0;
	}

	if((u32)offset > fnode->u.file.len) {
		if (U_SUCC != _SpreadObjectSize(obj, offset - fnode->u.file.len)) {
			uffs_Perror(UFFS_ERR_NOISY, PFX"Can't write file out of range!\n");
			uffs_ObjectDevUnLock(obj);
			return 0;
		}
	}

	obj->pos = offset;

	while(remain > 0) {
		write_start = obj->pos + len - remain;
		if(write_start > fnode->u.file.len) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"write point out of file ?\n");
			break;
		}

		fdn = _GetFdnByOfs(obj, write_start);

		if(write_start == fnode->u.file.len && fdn > 0 &&
			write_start == _GetStartOfDataBlock(obj, fdn)) {
			if(dev->tree.erasedCount < MINIMUN_ERASED_BLOCK) {
				uffs_Perror(UFFS_ERR_NOISY, PFX"insufficient block in write obj, new block\n");
				obj->err = UENOSPACE;
				break;
			}
			size = _WriteNewBlock(obj, (u8 *)data + len - remain, remain, fnode->u.file.serial, fdn);

			//Flush immediately, so that the new data node will be created and put in the tree.
			uffs_BufFlush(dev);

			if(size == 0) break;
			remain -= size;
		}
		else {
			if(fdn == 0)
				dnode = obj->node;
			else
				dnode = uffs_FindDataNode(dev, fnode->u.file.serial, fdn);

			if(dnode == NULL) {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't find data node in tree ?\n");
				break;
			}
			size = _WriteInternalBlock(obj, dnode, fdn,
									(u8 *)data + len - remain, remain,
									write_start - _GetStartOfDataBlock(obj, fdn));
#ifdef FLUSH_BUF_AFTER_WRITE
			uffs_BufFlush(dev);
#endif
			if(size == 0) break;
			remain -= size;
		}
	}

	obj->pos += (len - remain);

	if (HAVE_BADBLOCK(dev)) uffs_RecoverBadBlock(dev);

	uffs_ObjectDevUnLock(obj);

	return len - remain;
}

/**
 * read data from obj
 * \param[in] obj uffs object
 * \param[out] data output data buffer
 * \param[in] len required length of data to be read from object->pos
 * \return return bytes of data have been read
 */
int uffs_ReadObject(uffs_Object *obj, void *data, int offset, int len)
{
	uffs_Device *dev = obj->dev;
	TreeNode *fnode = obj->node;
	u32 remain = len;
	u16 fdn;
	u32 read_start;
	TreeNode *dnode;
	u32 size;
	uffs_Buf *buf;
	u32 blockOfs;
	u16 pageID;
	u8 type;
	u32 pageOfs;

	if (obj == NULL) return 0;
	if (obj->dev == NULL || obj->openSucc == U_FALSE) return 0;


	if (obj->type == UFFS_TYPE_DIR) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't read from a dir object!\n");
		return 0;
	}


	if(offset > fnode->u.file.len) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't read file out of range!\n");
		return 0;
	}

//	if(obj->oflag & UO_WRONLY) return 0;

    obj->pos = offset;
	uffs_ObjectDevLock(obj);

	while(remain > 0) {
		read_start = obj->pos + len - remain;
		if(read_start >= fnode->u.file.len) {
			uffs_Perror(UFFS_ERR_NOISY, PFX"read point out of file ?\n");
			break;
		}

		fdn = _GetFdnByOfs(obj, read_start);
		if(fdn == 0) {
			dnode = obj->node;
			type = UFFS_TYPE_FILE;
		}
		else {
			type = UFFS_TYPE_DATA;
			dnode = uffs_FindDataNode(dev, fnode->u.file.serial, fdn);
			if(dnode == NULL) {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get data node in entry!\n");
				break;
			}
		}

		blockOfs = _GetStartOfDataBlock(obj, fdn);
		pageID = (read_start - blockOfs) / dev->com.pgDataSize;

		if(fdn == 0) {
			/**
			 * fdn == 0: this means that the reading range is start from the first block,
			 * since the page 0 is for file attr, so we move to the next page ID.
			 */
			pageID++;
		}

		buf = uffs_BufGetEx(dev, type, dnode, (u16)pageID);
		if(buf == NULL) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get buffer when read obj.\n");
			break;
		}

		pageOfs = read_start % dev->com.pgDataSize;
		if(pageOfs >= buf->dataLen) {
			uffs_Perror(UFFS_ERR_NOISY, PFX"read data out of page range ?\n");
			uffs_BufPut(dev, buf);
			break;
		}
		size = (remain + pageOfs > buf->dataLen ? buf->dataLen - pageOfs: remain);

		uffs_BufRead(dev, buf, (u8 *)data + len - remain, pageOfs, size);
		uffs_BufPut(dev, buf);

		remain -= size;
	}

	obj->pos += (len - remain);

	if (HAVE_BADBLOCK(dev)) uffs_RecoverBadBlock(dev);

	uffs_ObjectDevUnLock(obj);

	return len - remain;
}

/**
 * move the file pointer
 * \param[in] obj uffs object
 * \param[in] offset offset from origin
 * \param[in] origin the origin position, one of:
 * \return return the new file pointer position
 */
long uffs_SeekObject(uffs_Object *obj, long offset, int origin)
{
	if (obj->type == UFFS_TYPE_DIR) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't seek a dir object!\n");
		return 0;
	}

	uffs_ObjectDevLock(obj);

	switch(origin) {
		case USEEK_CUR:
			if(obj->pos + offset > obj->node->u.file.len) {
				obj->pos = obj->node->u.file.len;
			}
			else {
				obj->pos += offset;
			}
			break;
		case USEEK_SET:
			if(offset > (long) obj->node->u.file.len) {
				obj->pos = obj->node->u.file.len;
			}
			else {
				obj->pos = offset;
			}
			break;
		case USEEK_END:
			if ( offset>0 ) {
				obj->pos = obj->node->u.file.len;
			}
			else if((offset >= 0 ? offset : -offset) > (long) obj->node->u.file.len) {
				obj->pos = 0;
			}
			else {
				obj->pos = obj->node->u.file.len + offset;
			}
			break;
	}

	uffs_ObjectDevUnLock(obj);

	return (long) obj->pos;
}

/**
 * return current file pointer
 * \param[in] obj uffs object
 * \return return the file pointer position if the obj is valid, return -1 if obj is invalid.
 */
int uffs_GetCurOffset(uffs_Object *obj)
{
	if (obj) {
		if (obj->dev && obj->openSucc == U_TRUE)
			return obj->pos;
	}
	return -1;
}

int uffs_SyncObject(uffs_Object *obj)
{
	uffs_Device *dev;

	if(obj == NULL || obj->dev == NULL) {
	    return U_FAIL;
	}

	if (obj->openSucc != U_TRUE) {
	    return U_FAIL;
	}

	dev = obj->dev;
	uffs_ObjectDevLock(obj);

	if(obj->oflag & (UO_WRONLY|UO_RDWR|UO_APPEND|UO_CREATE|UO_TRUNC)) {
		uffs_BufFlush(dev);
	}
	uffs_ObjectDevUnLock(obj);

	return U_SUCC;
}

/**
 * check whether the file pointer is at the end of file
 * \param[in] obj uffs object
 * \return return 1 if file pointer is at the end of file, return -1 if error occur, else return 0.
 */
int uffs_EndOfFile(uffs_Object *obj)
{
	if (obj) {
		if (obj->dev && obj->type == UFFS_TYPE_FILE && obj->openSucc == U_TRUE) {
			if (obj->pos >= obj->node->u.file.len) {
				return 1;
			}
			else {
				return 0;
			}
		}
	}

	return -1;
}

static URET _CoverOnePage(uffs_Device *dev,
						  uffs_Tags *old,
						  uffs_Tags *newTag,
						  u16 newBlock,
						  u16 page,
						  int newTimeStamp,
						  uffs_Buf *buf,
						  u32 length)
{
	newTag->father = buf->father;
	newTag->serial = buf->serial;
	newTag->type = buf->type;
	newTag->blockTimeStamp = newTimeStamp;
	newTag->dataLength = length;
	newTag->dataSum = old->dataSum;
	newTag->pageID = (u8)(buf->pageID);


	return uffs_WriteDataToNewPage(dev, newBlock, page, newTag, buf);
}

static URET _TruncateInternalWithBlockRecover(uffs_Object *obj, u16 fdn, u32 remain)
{
	uffs_Device *dev = obj->dev;
	TreeNode *fnode = obj->node;
	u16 pageID, maxPageID;
	TreeNode *node, *newNode = NULL;
	u16 block = UFFS_INVALID_BLOCK, newBlock = UFFS_INVALID_BLOCK;
	uffs_blockInfo *bc = NULL, *newBc = NULL;
	uffs_Buf *buf = NULL;
	uffs_Tags *tag, *newTag;
	URET ret = U_FAIL;
	u8 type = UFFS_TYPE_RESV;
	u32 startOfBlock;
	u32 end;
	int timeStamp;
	u16 page;

	if(fdn == 0) {
		node = fnode;
		block = node->u.file.block;
		type = UFFS_TYPE_FILE;
		maxPageID = obj->pagesOnHead;
	}
	else {
		node = uffs_FindDataNode(dev, fnode->u.file.serial, fdn);
		if(node == NULL) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't find data node when truncate obj\n");
			goto _err;
		}
		block = node->u.data.block;
		type = UFFS_TYPE_DATA;
		maxPageID = dev->attr.pages_per_block - 1;
	}


	bc = uffs_GetBlockInfo(dev, block);
	if(bc == NULL) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get block info when truncate obj\n");
		goto _err;
	}

	newNode = uffs_GetErased(dev);
	if(newNode == NULL) {
		uffs_Perror(UFFS_ERR_NOISY, PFX"insufficient erased block, can't truncate obj.\n");
		goto _err;
	}
	newBlock = newNode->u.list.block;
	newBc = uffs_GetBlockInfo(dev, newBlock);
	if(newBc == NULL) {
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get block info when truncate obj\n");
		goto _err;
	}

	startOfBlock = _GetStartOfDataBlock(obj, fdn);
	timeStamp = uffs_GetBlockTimeStamp(dev, bc);
	timeStamp = uffs_GetNextBlockTimeStamp(timeStamp);

	for(pageID = 0; pageID <= maxPageID; pageID++) {
		page = uffs_FindPageInBlockWithPageId(dev, bc, pageID);
		if(page == UFFS_INVALID_PAGE) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"unknown error, truncate\n");
			break;
		}
		page = uffs_FindBestPageInBlock(dev, bc, page);
		buf = uffs_BufClone(dev, NULL);
		if(buf == NULL) {
			uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't clone page buffer\n");
			goto _err;
		}
		tag = &(bc->spares[page].tag);
		uffs_LoadPhiDataToBuf(dev, buf, bc->blockNum, page);

		buf->father = tag->father;
		buf->serial = tag->serial;
		buf->type = tag->type;
		buf->pageID = tag->pageID;
		buf->dataLen = tag->dataLength;

		newTag = &(newBc->spares[pageID].tag);
		if(fdn == 0 && pageID == 0) {
			//copy the page file infomation
			ret = _CoverOnePage(dev, tag, newTag, newBlock, pageID, timeStamp, buf, buf->dataLen);
			if(ret != U_SUCC) break;
		}
		else {
			end = ((fdn == 0) ? (pageID - 1) * dev->com.pgDataSize :
					pageID * dev->com.pgDataSize);
			end += tag->dataLength;
			end += startOfBlock;

			if(remain > end) {
				if(tag->dataLength != dev->com.pgDataSize) {
					uffs_Perror(UFFS_ERR_NOISY, PFX" ???? unknown error when truncate. \n");
					break;
				}
				ret = _CoverOnePage(dev, tag, newTag, newBlock, pageID, timeStamp, buf, buf->dataLen);
				if(ret != U_SUCC) break;
			}
			else if(remain == end) {
				ret = _CoverOnePage(dev, tag, newTag, newBlock, pageID, timeStamp, buf, buf->dataLen);
				if(ret != U_SUCC) break;
			}
			else if(remain < end) {
				buf->dataLen = tag->dataLength - (end - remain);
				if(buf->dataLen == 0) {
					ret = U_SUCC;
					break;
				}
				memset(buf->data + buf->dataLen, 0, dev->com.pgDataSize - buf->dataLen);
				ret = _CoverOnePage(dev, tag, newTag, newBlock, pageID, timeStamp, buf, buf->dataLen);
				break;
			}
		}
		uffs_BufFreeClone(dev, buf);
		buf = NULL;
	}

_err:
	if(buf != NULL) {
		uffs_BufFreeClone(dev, buf);
		buf = NULL;
	}
	if(ret == U_SUCC) {
		//ok, modify the tree, and erase old block
		//NOTE: Don't delete the 'old' node from tree, just replace the 'block' with new block,
		//      so that we don't need to modify obj->node :)
		uffs_SetTreeNodeBlock(type, node, newNode->u.list.block);
		newNode->u.list.block = block;
		dev->ops->EraseBlock(dev, newNode->u.list.block);
		uffs_InsertToErasedListTail(dev, newNode);
	}
	else {
		//fail to cover block, so erase new block
		dev->ops->EraseBlock(dev, newBlock);
		uffs_InsertToErasedListTail(dev, newNode);
	}

	if(bc) uffs_ExpireBlockInfo(dev, bc, UFFS_ALL_PAGES);
	if(bc) uffs_PutBlockInfo(dev, bc);
	if(newBc) uffs_ExpireBlockInfo(dev, newBc, UFFS_ALL_PAGES);
	if(newBc) uffs_PutBlockInfo(dev, newBc);

	return U_SUCC;
}

URET uffs_TruncateObject(uffs_Object *obj, u32 remain)
{
	URET ret = U_SUCC;
	uffs_ObjectInfo info;

	uffs_ObjectDevLock(obj);
	if (U_SUCC != _LoadObjectInfo(obj->dev, obj->node, &info, obj->type)) {
		uffs_ObjectDevUnLock(obj);
		obj->err = UENOMEM;
		return U_FAIL;
	}

    if ((info.info.reserved != obj->handle) && !(info.info.attr & US_WRITE)) {
		obj->err = UESHARE;
		uffs_ObjectDevUnLock(obj);
		return U_FAIL;
	}

    if (!(info.info.attr & UA_RDWR)) {
		obj->err = UEACCESS;
		uffs_ObjectDevUnLock(obj);
		return U_FAIL;
	}

	if (obj->node->u.file.len == remain) {
		//..
	} else {
		ret = _TruncateObject(obj, remain);
	}
	uffs_ObjectDevUnLock(obj);

	return ret;
}

/** truncate obj without lock device */
static URET _TruncateObject(uffs_Object *obj, u32 remain)
{
	uffs_Device *dev = obj->dev;
	TreeNode *fnode = obj->node;
	u16 fdn;
	u32 flen;
	u32 startOfBlock;
	TreeNode *node;
	uffs_blockInfo *bc;
	uffs_Buf *buf;
	u16 page;

	if (obj == NULL) return U_FAIL;
	if (obj->dev == NULL || obj->openSucc == U_FALSE) return U_FAIL;

	/* do nothing if the obj is a dir */
	/* TODO: delete files under dir ? */
	if (obj->type == UFFS_TYPE_DIR) {
		obj->err = UEEXIST;
		return U_FAIL;
	}

	if(remain > fnode->u.file.len) return U_FAIL;

	uffs_BufFlush(dev); //flush dirty buffers first

	while(fnode->u.file.len > remain) {
		flen = fnode->u.file.len;
		fdn = _GetFdnByOfs(obj, flen - 1);

		startOfBlock = _GetStartOfDataBlock(obj, fdn);
		if(remain <= startOfBlock && fdn > 0) {
			node = uffs_FindDataNode(dev, fnode->u.file.serial, fdn);
			if(node == NULL) {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't find data node when trancate obj.\n");
				return U_FAIL;
			}
			bc = uffs_GetBlockInfo(dev, node->u.data.block);
			if(bc == NULL) {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get block info when trancate obj.\n");
				return U_FAIL;
			}
			for(page = 0; page < dev->attr.pages_per_block; page++) {
				buf = uffs_BufFind(dev, fnode->u.file.serial, fdn, page);
				if(buf) uffs_BufSetMark(buf, UFFS_BUF_EMPTY);
			}
			uffs_ExpireBlockInfo(dev, bc, UFFS_ALL_PAGES);
			dev->ops->EraseBlock(dev, node->u.data.block);
			uffs_BreakFromEntry(dev, UFFS_TYPE_DATA, node);
			node->u.list.block = bc->blockNum;
			uffs_PutBlockInfo(dev, bc);
			uffs_InsertToErasedListTail(dev, node);
			fnode->u.file.len = startOfBlock;
		}
		else {
			if(_TruncateInternalWithBlockRecover(obj, fdn, remain) == U_SUCC) {
				fnode->u.file.len = remain;
			}
			else {
				uffs_Perror(UFFS_ERR_SERIOUS, PFX"fail to truncate obj\n");
				return U_FAIL;
			}
		}
	}

	if (HAVE_BADBLOCK(dev)) uffs_RecoverBadBlock(dev);

	return U_SUCC;

}


/**
 * delete uffs object
 */
URET uffs_DeleteObjectWithPreOpen(uffs_Object *obj)
{
	TreeNode *node;
	uffs_Device *dev;
	u16 block;
	uffs_Buf *buf;
	URET ret = U_FAIL;

    uffs_ObjectDevLock(obj);

    ret = _OpenObjectUnderForDelete(obj);
    if (ret == U_FAIL) {
		goto err;
	}

	_TruncateObject(obj, 0);

	dev = obj->dev;

	if (obj->type == UFFS_TYPE_DIR) {
		// if the dir is not empty, can't delete it.
		node = uffs_FindDirNodeFromTreeWithFather(dev, obj->serial);
		if (node != NULL) goto err;  //have sub dirs ?

		node = uffs_FindFileNodeFromTreeWithFather(dev, obj->serial);
		if (node != NULL) goto err;  //have sub files ?
	}

	block = GET_BLOCK_FROM_NODE(obj);
	node = obj->node;

	// before erase the block, we need to take care of the buffer ...
	uffs_BufFlush(dev);
	if (HAVE_BADBLOCK(dev)) uffs_RecoverBadBlock(dev);

	buf = uffs_BufFind(dev, obj->father, obj->serial, 0);
	if (buf) {
		//need to expire this buffer ...
		if (buf->refCount != 0) {
			//there is other obj for this file still in use ?
			uffs_Perror(UFFS_ERR_NORMAL, PFX"Try to delete object but still have buf referenced.\n");
			goto err;
		}
		buf->mark = UFFS_BUF_EMPTY; //!< make this buffer expired.
	}
	//FIXME !!: need to take care of other obj->node ?
	uffs_BreakFromEntry(dev, obj->type, node);
	dev->ops->EraseBlock(dev, block); //!< ok, the object is deleted now.
	node->u.list.block = block;
	uffs_InsertToErasedListTail(dev, node);

	ret = U_SUCC;
err:
	uffs_ObjectDevUnLock(obj);

	_ReleaseObjectResource(obj);

	return ret;
}

URET uffs_RenameObjectWithPreOpen(uffs_Object *obj, char *oldPath, int newFather, char *buf_new)
{
	uffs_Device *dev;
	TreeNode *node;
	uffs_Buf *buf;
	uffs_fileInfo fi;

	u16 newLen;
	u16 oldLen;

    u16 oldSum;
    u16 newSum;

    oldLen = strlen(oldPath);
    newLen = strlen(buf_new);

    if (buf_new[newLen-1] == '/') {
		buf_new[newLen-1] = 0;
		newLen--;
	}

	oldSum = uffs_MakeSum16(oldPath, oldLen);
	newSum = uffs_MakeSum16(buf_new, newLen);

	uffs_ObjectDevLock(obj);

//check old object be exist!
    obj->node = uffs_FindDirNodeByName(obj->dev, oldPath, oldLen, oldSum, obj->serial);
	if (obj->node == NULL) {
		obj->node = uffs_FindFileNodeByName(obj->dev, oldPath, oldLen, oldSum, obj->serial);
		obj->type = UFFS_TYPE_FILE;
	}

	if (obj->node == NULL) {
		obj->err = UENOENT;
		uffs_ObjectDevUnLock(obj);
		return U_FAIL;
	}

//check if has permission
	uffs_BufFlush(obj->dev);

	buf = uffs_BufGetEx(obj->dev, obj->type, obj->node, 0);
	if(buf == NULL) {
		obj->err = UENOMEM;
		uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get buf when open!\n");
		uffs_ObjectDevUnLock(obj);
		return U_FAIL;
	}

	memcpy(&fi, buf->data, sizeof(uffs_fileInfo));
	if ((fi.attr & US_DENY) || (fi.attr & 0x000000ff)) {
	    obj->err = UESHARE;
		obj->openSucc = U_FALSE;
		uffs_BufPut(obj->dev, buf);
		uffs_ObjectDevUnLock(obj);
		return U_FAIL;
	}

//check new object isn't exist!
    if (obj->serial == newFather && !strcmp(oldPath, buf_new)) {
    	uffs_ObjectDevUnLock(obj);
    	uffs_BufPut(obj->dev, buf);
        return U_SUCC;
    }

	node = uffs_FindDirNodeByName(obj->dev, buf_new, newLen, newSum, newFather);
	if (node == NULL) {
		node = uffs_FindFileNodeByName(obj->dev, buf_new, newLen, newSum, newFather);
	}

	if(node == NULL) {
		//..
	} else {
   	    obj->err = UEINVAL;
   	    uffs_ObjectDevUnLock(obj);
   	    uffs_BufPut(obj->dev, buf);
   	    return U_FAIL;
	}

	dev = obj->dev;
	node = obj->node;

	fi.name_len = newLen;
	memcpy((char *)(fi.name), buf_new, newLen + 1);
	fi.lastModify = uffs_GetCurDateTime();

	obj->sum = uffs_MakeSum16(fi.name, fi.name_len);

	//update the check sum of tree node
	if (obj->type == UFFS_TYPE_DIR) {
		obj->node->u.dir.checkSum = obj->sum;
		obj->node->u.dir.father = newFather;
	}
	else {
		obj->node->u.file.checkSum = obj->sum;
		obj->node->u.file.father = newFather;
	}

	buf->father = newFather;	// !! need to manually change the 'father' !!

	uffs_BufWrite(dev, buf, &fi, 0, sizeof(uffs_fileInfo));
	uffs_BufPut(dev, buf);
	uffs_BufFlushEx(dev, U_TRUE);	// !! force a block recover so that all old tag will be expired !!

	uffs_ObjectDevUnLock(obj);

	return U_SUCC;

}

//URET uffs_RenameObject(const char *old_name, const char *new_name)
//{
//	uffs_Object obj, obj_new;
//	uffs_Device *dev;
//	TreeNode *node;
//	uffs_Buf *buf;
//	uffs_fileInfo fi;
//	char buf_old[MAX_PATH_LENGTH+2]= {0};
//	char buf_new[MAX_PATH_LENGTH+2]= {0};
//	int pos, pos1;
//	u16 father_new;
//
//	if(strlen(old_name) >= MAX_PATH_LENGTH ||
//		strlen(new_name) >= MAX_PATH_LENGTH) return U_FAIL;
//
//	if (uffs_GetAbsolutePath(old_name, buf_old) != U_SUCC) return U_FAIL;
//	if (uffs_GetAbsolutePath(new_name, buf_new) != U_SUCC) return U_FAIL;
//
//	buf_old[_getpath(buf_old, NULL)] = 0;
//	buf_new[_getpath(buf_new, NULL)] = 0;
//	pos = find_maxMatchedMountPoint(buf_old);
//	pos1 = find_maxMatchedMountPoint(buf_new);
//	if (pos <= 0 || pos <= 0 || pos != pos1) {
//		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't moving object to different mount point\n");
//		return U_FAIL;
//	}
//
//	uffs_GetAbsolutePath(old_name, buf_old);
//	uffs_GetAbsolutePath(new_name, buf_new);
//
//	/* check whether the new object exist ? */
//	memset(&obj, 0, sizeof(uffs_Object));
//
//	if (uffs_OpenObject(&obj, buf_new, UO_DIR|UO_DELETE, US_DENY) == U_SUCC) {
//		uffs_CloseObject(&obj);
//		return U_FAIL;
//	}
//	memset(&obj, 0, sizeof(uffs_Object));
//	if (uffs_OpenObject(&obj, buf_new, UO_RDONLY, US_READ) == U_SUCC) {
//		uffs_CloseObject(&obj);
//		return U_FAIL;
//	}
//
//	/* check whether the old object exist ? */
//	memset(&obj, 0, sizeof(uffs_Object));
//	if (uffs_OpenObject(&obj, buf_old, UO_DIR|UO_RDONLY, US_DENY) == U_FAIL) {
//		memset(&obj, 0, sizeof(uffs_Object));
//		if (uffs_OpenObject(&obj, buf_old, UO_RDONLY, US_DENY) == U_FAIL) {
//			return U_FAIL;
//		}
//	}
//
//	/* get new object's farther's serail No. by prepare create the new object */
//	memset(&obj_new, 0, sizeof(uffs_Object));
//	if (_PrepareOpenObj(&obj_new, buf_new, UO_RDWR, 0) == U_FAIL) {
//		return U_FAIL;
//	}
//	father_new = obj_new.father;
//
//	dev = obj.dev;
//	node = obj.node;
//
//	uffs_ObjectDevLock(&obj);
//
//	uffs_BufFlush(dev);
//
//	buf = uffs_BufGetEx(dev, obj.type, node, 0);
//	if(buf == NULL) {
//		uffs_Perror(UFFS_ERR_SERIOUS, PFX"can't get buf when rename!\n");
//		uffs_ObjectDevUnLock(&obj);
//		uffs_CloseObject(&obj);
//		_ReleaseObjectResource(&obj_new);
//		return U_FAIL;
//	}
//
//	memcpy(&fi, buf->data, sizeof(uffs_fileInfo));
//	if (buf_new[strlen(buf_new)-1] == '/') buf_new[strlen(buf_new)-1] = 0;
//	strcpy((char *)(fi.name), _getname(buf_new));
//	fi.name_len = strlen(fi.name);
//	fi.lastModify = uffs_GetCurDateTime();
//
//	obj.sum = uffs_MakeSum16(fi.name, fi.name_len);
//
//	//update the check sum of tree node
//	if (obj.type == UFFS_TYPE_DIR) {
//		obj.node->u.dir.checkSum = obj.sum;
//		obj.node->u.dir.father = father_new;
//	}
//	else {
//		obj.node->u.file.checkSum = obj.sum;
//		obj.node->u.file.father = father_new;
//	}
//
//	buf->father = father_new;	// !! need to manually change the 'father' !!
//
//	uffs_BufWrite(dev, buf, &fi, 0, sizeof(uffs_fileInfo));
//	uffs_BufPut(dev, buf);
//	uffs_BufFlushEx(dev, U_TRUE);	// !! force a block recover so that all old tag will be expired !!
//
//	uffs_ObjectDevUnLock(&obj);
//	uffs_CloseObject(&obj);
//
//    _ReleaseObjectResource(&obj_new);
//	return U_SUCC;
//
//}

URET uffs_GetObjectInfo(uffs_Object *obj, uffs_ObjectInfo *info)
{
	uffs_Device *dev = obj->dev;
	URET ret = U_FAIL;

	uffs_ObjectDevLock(obj);

	if (obj && obj->dev && info)
		ret = _LoadObjectInfo(dev, obj->node, info, obj->type);

    if (ret == U_SUCC) {
        if (info->info.reserved != obj->handle && (info->info.attr & US_DENY)) {
            ret = U_FAIL;
        }
	}

	uffs_ObjectDevUnLock(obj);

	return ret;
}

URET uffs_SetObjectInfo(uffs_Object *obj, int data, int field)
{
	uffs_Device *dev = obj->dev;
	URET ret = U_FAIL;

	uffs_ObjectDevLock(obj);

	if (obj && dev)
		ret = _PutObjectInfo(obj, obj->node, data, field, obj->type);

	uffs_ObjectDevUnLock(obj);

	return ret;
}

/* find objects */
URET uffs_OpenFindObject(uffs_FindInfo *f, int father, char *dir)
{
	URET ret;
	if (f == NULL) return U_FAIL;

//	f->obj = uffs_GetObject();
//	if (f->obj == NULL) {
//		uffs_Perror(UFFS_ERR_SERIOUS, PFX"Can't get a new object\n");
//		goto err;
//	}

	if (!strcmp(dir, "/")) {
    	if (_PrepareOpenObj(&(f->obj), dir, UO_RDONLY|UO_DIR, 0) == U_FAIL) {
    		uffs_Perror(UFFS_ERR_NOISY, PFX"Can't prepare open dir %s for read.\n", dir);
    		goto err;
    	}
		f->dev = f->obj.dev;
	} else {
    	ret = uffs_RePrepareObject(&(f->obj), dir, father, UO_DIR|UO_LOOKUP, 0);
	    if (ret != U_SUCC) {
	        goto err;
	    }

		f->dev = f->obj.dev;

		uffs_ObjectDevLock(&(f->obj));
		if (_OpenObjectUnder2(&(f->obj)) == U_FAIL) {
			uffs_ObjectDevUnLock(&(f->obj));
			uffs_Perror(UFFS_ERR_NOISY, PFX"Can't open dir %s, doesn't exist ?\n", dir);
			goto err;
		}
		uffs_ObjectDevUnLock(&(f->obj));
	}

	f->hash = 0;
	f->work = NULL;
	f->step = 3;

	return U_SUCC;

err:
	if (f) {
		_ReleaseObjectResource(&(f->obj));
		if (!strcmp(dir, "/")) {
		    uffs_PutObject(&(f->obj));
		}
	}

	return U_FAIL;
}

URET uffs_FindFirstObject(uffs_ObjectInfo * info, uffs_FindInfo * f)
{
	uffs_Device *dev = f->dev;
	TreeNode *node;
	u16 x;
	URET ret = U_SUCC;

	uffs_DeviceLock(dev);
	f->step = 0;

	if (f->step == 0) {
		for(f->hash = 0;
			f->hash < DIR_NODE_ENTRY_LEN;
			f->hash++) {

			x = dev->tree.dirEntry[f->hash];

			while(x != EMPTY_NODE) {
				node = FROM_IDX(x, &(dev->tree.dis));
				if(node->u.dir.father == f->obj.serial) {
					f->work = node;
					if (info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_DIR);
					uffs_DeviceUnLock(dev);
					return ret;
				}
				x = node->hashNext;
			}
		}

		//no subdirs, then lookup the files
		f->step++;
	}

	if(f->step == 1) {
		for(f->hash = 0;
			f->hash < FILE_NODE_ENTRY_LEN;
			f->hash++) {

			x = dev->tree.fileEntry[f->hash];

			while(x != EMPTY_NODE) {
				node = FROM_IDX(x, &(dev->tree.dis));
				if(node->u.file.father == f->obj.serial) {
					f->work = node;
					if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_FILE);
					uffs_DeviceUnLock(dev);
					return ret;
				}
				x = node->hashNext;
			}
		}
		f->step++;
	}

	uffs_DeviceUnLock(dev);
	return U_FAIL;
}

URET uffs_FindNextObject(uffs_ObjectInfo *info, uffs_FindInfo * f)
{
	uffs_Device *dev = f->dev;
	TreeNode *node;
	u16 x;
	URET ret = U_SUCC;

	if(f->dev == NULL ||
		f->work == NULL ||
		f->step > 1) return U_FAIL;

	uffs_DeviceLock(dev);

	x = f->work->hashNext;

	if (f->step == 0) { //!< working on dirs
		while(x != EMPTY_NODE) {
			node = FROM_IDX(x, &(dev->tree.dis));
			if(node->u.dir.father == f->obj.serial) {
				f->work = node;
				if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_DIR);
				uffs_DeviceUnLock(dev);
				return ret;
			}
			x = node->hashNext;
		}

		f->hash++; //come to next hash entry

		for(; f->hash < DIR_NODE_ENTRY_LEN; f->hash++) {
			x = dev->tree.dirEntry[f->hash];
			while(x != EMPTY_NODE) {
				node = FROM_IDX(x, &(dev->tree.dis));
				if(node->u.dir.father == f->obj.serial) {
					f->work = node;
					if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_DIR);
					uffs_DeviceUnLock(dev);
					return ret;
				}
				x = node->hashNext;
			}
		}

		//no subdirs, then lookup files ..
		f->step++;
		f->hash = 0;
		x = EMPTY_NODE;
	}

	if (f->step == 1) {

		while(x != EMPTY_NODE) {
			node = FROM_IDX(x, &(dev->tree.dis));
			if(node->u.file.father == f->obj.serial) {
				f->work = node;
				if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_FILE);
				uffs_DeviceUnLock(dev);
				return ret;
			}
			x = node->hashNext;
		}

		f->hash++; //come to next hash entry

		for(; f->hash < FILE_NODE_ENTRY_LEN; f->hash++) {
			x = dev->tree.fileEntry[f->hash];
			while(x != EMPTY_NODE) {
				node = FROM_IDX(x, &(dev->tree.dis));
				if(node->u.file.father == f->obj.serial) {
					f->work = node;
					if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_FILE);
					uffs_DeviceUnLock(dev);
					return ret;
				}
				x = node->hashNext;
			}
		}

		//no any files, stoped.
		f->step++;
	}

	uffs_DeviceUnLock(dev);
	return U_FAIL;
}

URET uffs_MoveNextObject(uffs_ObjectInfo * info, uffs_FindInfo * f)
{
	uffs_Device *dev = f->dev;
	TreeNode *node;
	u16 x;
	URET ret = U_SUCC;

	if(f->dev == NULL || f->step < 0) return U_FAIL;

	uffs_DeviceLock(dev);

    if (f->step == 3) {
        strcpy(info->info.name, ".");
        f->step = 4;
        uffs_DeviceUnLock(dev);
        return ret;
    }
    if (f->step == 4) {
        strcpy(info->info.name, "..");
        f->step = 5;
        uffs_DeviceUnLock(dev);
        return ret;
    }

    if (f->step == 5) {
        f->step = 0;
    	if (f->step == 0) {
    		for(f->hash = 0;
    			f->hash < DIR_NODE_ENTRY_LEN;
    			f->hash++) {

    			x = dev->tree.dirEntry[f->hash];

    			while(x != EMPTY_NODE) {
    				node = FROM_IDX(x, &(dev->tree.dis));
    				if(node->u.dir.father == f->obj.serial) {
    					f->work = node;
    					if (info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_DIR);
    					uffs_DeviceUnLock(dev);
    					return ret;
    				}
    				x = node->hashNext;
    			}
    		}

    		//no subdirs, then lookup the files
    		f->step++;
    	}

    	if(f->step == 1) {
    		for(f->hash = 0;
    			f->hash < FILE_NODE_ENTRY_LEN;
    			f->hash++) {

    			x = dev->tree.fileEntry[f->hash];

    			while(x != EMPTY_NODE) {
    				node = FROM_IDX(x, &(dev->tree.dis));
    				if(node->u.file.father == f->obj.serial) {
    					f->work = node;
    					if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_FILE);
    					uffs_DeviceUnLock(dev);
    					return ret;
    				}
    				x = node->hashNext;
    			}
    		}
    		f->step++;
    	}
    } else {
    //end of findfirst
//begin findnext
        if (f->work == NULL) {
            return U_FAIL;
        }
    	x = f->work->hashNext;

    	if (f->step == 0) { //!< working on dirs
    		while(x != EMPTY_NODE) {
    			node = FROM_IDX(x, &(dev->tree.dis));
    			if(node->u.dir.father == f->obj.serial) {
    				f->work = node;
    				if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_DIR);
    				uffs_DeviceUnLock(dev);
    				return ret;
    			}
    			x = node->hashNext;
    		}

    		f->hash++; //come to next hash entry

    		for(; f->hash < DIR_NODE_ENTRY_LEN; f->hash++) {
    			x = dev->tree.dirEntry[f->hash];
    			while(x != EMPTY_NODE) {
    				node = FROM_IDX(x, &(dev->tree.dis));
    				if(node->u.dir.father == f->obj.serial) {
    					f->work = node;
    					if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_DIR);
    					uffs_DeviceUnLock(dev);
    					return ret;
    				}
    				x = node->hashNext;
    			}
    		}

    		//no subdirs, then lookup files ..
    		f->step++;
    		f->hash = 0;
    		x = EMPTY_NODE;
    	}

    	if (f->step == 1) {

    		while(x != EMPTY_NODE) {
    			node = FROM_IDX(x, &(dev->tree.dis));
    			if(node->u.file.father == f->obj.serial) {
    				f->work = node;
    				if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_FILE);
    				uffs_DeviceUnLock(dev);
    				return ret;
    			}
    			x = node->hashNext;
    		}

    		f->hash++; //come to next hash entry

    		for(; f->hash < FILE_NODE_ENTRY_LEN; f->hash++) {
    			x = dev->tree.fileEntry[f->hash];
    			while(x != EMPTY_NODE) {
    				node = FROM_IDX(x, &(dev->tree.dis));
    				if(node->u.file.father == f->obj.serial) {
    					f->work = node;
    					if(info) ret = _LoadObjectInfo(dev, node, info, UFFS_TYPE_FILE);
    					uffs_DeviceUnLock(dev);
    					return ret;
    				}
    				x = node->hashNext;
    			}
    		}

    		//no any files, stoped.
    		f->step++;
    	}
    }

	uffs_DeviceUnLock(dev);
	return U_FAIL;
}

URET uffs_FindResetObject(uffs_FindInfo *info)
{
    info->step = 3;
    info->work = NULL;
    info->hash = 0;
    return U_SUCC;
}

URET uffs_CloseFindObject(uffs_FindInfo * f)
{
	if (f == NULL) return U_FAIL;

	uffs_Buf *buf;
	uffs_fileInfo fi;
	uffs_Device *dev;
    uffs_Object *obj = &(f->obj);

	if (f) {
		/* close dir */
		if(obj->dev != NULL && obj->node) {
		    dev = obj->dev;
            uffs_ObjectDevLock(obj);

			//need to change the last modify time stamp
			if (obj->type == UFFS_TYPE_DIR)
				buf = uffs_BufGetEx(dev, UFFS_TYPE_DIR, obj->node, 0);
			else
				buf = uffs_BufGetEx(dev, UFFS_TYPE_FILE, obj->node, 0);

			if(buf != NULL) {
    			uffs_BufRead(dev, buf, &fi, 0, sizeof(uffs_fileInfo));
    			//fi.lastModify = uffs_GetCurDateTime();
    			if (fi.reserved == obj->handle) {
    			    fi.attr &= ~US_MASK;
                    fi.attr |= US_WRITE;

    				fi.reserved = 0;
    			}
    			fi.attr--;

    			uffs_BufWrite(dev, buf, &fi, 0, sizeof(uffs_fileInfo));
    			uffs_BufPut(dev, buf);
    	    }
    	    uffs_BufFlush(dev);

    	    uffs_ObjectDevUnLock(obj);
        }

        if (obj->name && !strcmp(obj->name, "/")) {
            _ReleaseObjectResource(obj);
		    uffs_PutObject(obj);
		} else {
		    _ReleaseObjectResource(obj);
		}
//	    f->obj = NULL;
    }

	f->dev = NULL;
	f->hash = 0;
	f->work = NULL;

	return U_SUCC;
}
